package network

import (
	"context"
	"errors"
	"fmt"
	"github.com/golang/protobuf/proto"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"google.golang.org/grpc"
	"google.golang.org/grpc/keepalive"
	"google.golang.org/grpc/reflection"
	"inventory/pkg/ecode"
	"inventory/pkg/env"
	"inventory/pkg/log"
	"net"
	"net/http"
	"net/http/pprof"
	"time"
)

type NetWork struct {
	config                                  *Config
	httpListener, perfListener, rpcListener net.Listener
	HttpServer, PerfServer                  *http.Server
	RpcServer                               *grpc.Server
	RpcVirtualClient                        *grpc.ClientConn
	ServeMux                                *runtime.ServeMux
}

type ProcessorMapKey struct {
	Path   string
	Method string
}

const (
	httpMethodKey    = "http-method"
	httpPathKey      = "http-path"
	HttpRawBody      = "http-raw"
	rpcMethodHeader  = runtime.MetadataHeaderPrefix + httpMethodKey
	rpcPathHeader    = runtime.MetadataHeaderPrefix + httpPathKey
	RpcRawBodyHeader = runtime.MetadataHeaderPrefix + HttpRawBody
)

func (n *NetWork) initHTTPServer(ctx context.Context, opts ...runtime.ServeMuxOption) (err error) {
	if n.httpListener, err = net.Listen("tcp", fmt.Sprintf(":"+n.config.HTTPPort)); err != nil {
		err = fmt.Errorf("HTTP服务端口%s监听失败: error=%v", n.config.HTTPPort, err.Error())
		return
	}
	// Register grpc-gateway
	n.ServeMux = runtime.NewServeMux(opts...)
	n.HttpServer = &http.Server{
		ReadTimeout:       n.config.ReadTimeout * time.Second,
		ReadHeaderTimeout: n.config.ReadHeaderTimeout * time.Second,
		WriteTimeout:      n.config.WriteTimeout * time.Second,
		IdleTimeout:       n.config.IdleTimeout * time.Second,
	}
	log.FromContext(ctx).Infof("http服务启动...监听端口%s", n.config.HTTPPort)
	return
}

func (n *NetWork) initPerfServer(ctx context.Context) (err error) {
	if n.perfListener, err = net.Listen("tcp", fmt.Sprintf(":"+n.config.PerfPort)); err != nil {
		err = fmt.Errorf("性能监控服务端口%s监听失败: error=%v", n.config.PerfPort, err.Error())
		return
	}
	mux := http.NewServeMux()
	mux.HandleFunc("/debug/pprof/", pprof.Index)
	mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
	mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
	mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	n.PerfServer = &http.Server{
		Handler:           mux,
		ReadTimeout:       n.config.ReadTimeout * time.Second,
		ReadHeaderTimeout: n.config.ReadHeaderTimeout * time.Second,
		WriteTimeout:      n.config.WriteTimeout * time.Second,
		IdleTimeout:       n.config.IdleTimeout * time.Second,
	}
	log.FromContext(ctx).Infof("perf服务启动...监听端口%s", n.config.PerfPort)
	return
}

func (n *NetWork) initRPCServer(ctx context.Context, envType env.Type, serviceName string,
	errorFormatter func(*ecode.ErrorCode) proto.Message,
	tokenInterceptor func(context.Context, interface{}) (context.Context, error)) (err error) {
	if n.rpcListener, err = net.Listen("tcp", fmt.Sprintf(":"+n.config.RPCPort)); err != nil {
		err = fmt.Errorf("RPC服务端口%s监听失败: error=%v", n.config.RPCPort, err.Error())
		return
	}
	n.RpcServer = grpc.NewServer(grpc.KeepaliveParams(keepalive.ServerParameters{
		MaxConnectionIdle:     n.config.IdleTimeout * time.Second,
		MaxConnectionAgeGrace: n.config.ForceCloseWait * time.Second,
		Time:                  n.config.KeepAliveInterval * time.Second,
		Timeout:               n.config.RPCTimeout * time.Second,
		MaxConnectionAge:      n.config.MaxLifeTime * time.Second,
	}), grpc.ChainUnaryInterceptor(
		errorInterceptor(envType, serviceName), n.logInterceptor(
			envType, serviceName, errorFormatter, tokenInterceptor),
	))
	if n.RpcVirtualClient, err = grpc.Dial("localhost:"+n.config.RPCPort, grpc.WithInsecure()); err != nil {
		err = fmt.Errorf("RPC服务端口%s创建虚拟链接失败: error=%v", n.config.RPCPort, err.Error())
		return
	}
	log.FromContext(ctx).Infof("rpc服务启动...监听端口%s", n.config.RPCPort)
	return
}

func New(ctx context.Context, config *Config, env env.Type, serviceName string,
	errorFormatter func(*ecode.ErrorCode) proto.Message,
	tokenInterceptor func(context.Context, interface{}) (context.Context, error),
	opts ...runtime.ServeMuxOption) (server *NetWork, err error) {
	server = &NetWork{config: config}
	if err = server.initRPCServer(ctx, env, serviceName, errorFormatter, tokenInterceptor); err != nil {
		return
	}
	if err = server.initHTTPServer(ctx, opts...); err != nil {
		return
	}
	if err = server.initPerfServer(ctx); err != nil {
		return
	}
	return
}

func (n *NetWork) serverRecover(serviceName string, err error, status chan<- error) {
	if r := recover(); r != nil {
		err = fmt.Errorf(ecode.GetPanicTrace(fmt.Sprintf("%s服务异常终止", serviceName), r))
		status <- err
	} else if err != nil {
		err = fmt.Errorf("%s服务启动失败: error=%s", serviceName, err.Error())
		status <- err
	} else {
		log.FromContext(context.Background()).Warnf("%s服务关闭...完成", serviceName)
	}
}

func (n *NetWork) StartServer(bodyPreProcessorMap map[ProcessorMapKey]func(r *http.Request)) (err error) {
	reflection.Register(n.RpcServer)
	//isProduction := envType == env.Production
	n.HttpServer.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "HEAD" && r.URL.Path == "/ping" {
			w.WriteHeader(http.StatusOK)
			return
		}
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Credentials", "true")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, B-Token, G-Token")
		w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, PATCH, DELETE")
		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusNoContent)
			return
		}
		r.Header.Set(rpcPathHeader, r.RequestURI)
		r.Header.Set(rpcMethodHeader, r.Method)
		if processor, ok := bodyPreProcessorMap[ProcessorMapKey{
			Path:   r.URL.Path,
			Method: r.Method,
		}]; ok {
			processor(r)
		}
		n.ServeMux.ServeHTTP(w, r)
	})
	status := make(chan error)
	go func() {
		select {
		case <-status:
			n.RpcServer.GracefulStop()
			n.RpcServer = nil
			err = <-status
		}
	}()
	go func() {
		defer n.serverRecover("perf", err, status)
		err = n.PerfServer.Serve(n.perfListener)
		n.PerfServer = nil
	}()
	go func() {
		defer n.serverRecover("http", err, status)
		err = n.HttpServer.Serve(n.httpListener)
		n.HttpServer = nil
	}()
	defer n.serverRecover("rpc", err, status)
	// rpc服务会block
	err = n.RpcServer.Serve(n.rpcListener)
	n.RpcServer = nil
	return
}

func (n *NetWork) ShutDown(ctx context.Context) {
	if n.PerfServer != nil {
		if err := n.PerfServer.Shutdown(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.FromContext(ctx).Error("perf服务关闭...失败")
		}
	}
	if n.HttpServer != nil {
		if err := n.HttpServer.Shutdown(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.FromContext(ctx).Error("http服务关闭...失败")
		}
	}
	if n.RpcServer != nil {
		_ = n.RpcVirtualClient.Close()
		n.RpcServer.GracefulStop()
	}
}
